{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "550b47f1-6e17-4ec6-9831-896b63ef0ce4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# File: cards.ipynb\n",
    "# Code: Claude Code and Codex\n",
    "# Review: Ryoichi Ando (ryoichi.ando@zozo.com)\n",
    "# License: Apache v2.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2af5eb5a-46aa-498b-bc01-08274d63100a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from frontend import App\n",
    "\n",
    "# create an app\n",
    "app = App.create(\"cards\")\n",
    "\n",
    "# create a rectangular card mesh\n",
    "mesh_res, n_stack, card_height = 8, 8, 0.25\n",
    "card_width = 0.75 * card_height\n",
    "V, F = app.mesh.rectangle(mesh_res, card_width, card_height, [0, 0, 1], [0, 1, 0])\n",
    "app.asset.add.tri(\"card\", V, F)\n",
    "\n",
    "# create a sphere mesh to knock down the house of cards\n",
    "V, F, T = app.mesh.icosphere(r=0.15, subdiv_count=3).tetrahedralize()\n",
    "app.asset.add.tet(\"sphere\", V, F, T)\n",
    "\n",
    "# create a scene\n",
    "scene = app.scene.create()\n",
    "angle, gap = 25.0, 1e-3\n",
    "\n",
    "\n",
    "# function to build a row of card pairs with ceiling cards on top\n",
    "def make_row(n: int, _x: float, y: float, cards: list) -> float:\n",
    "    x, _y, ceil_x = 0, 0, []\n",
    "    for i in range(n):\n",
    "        # add left card tilted at angle\n",
    "        left = scene.add(\"card\").rotate(-angle, \"z\")\n",
    "        left.at(x - left.min(\"x\") + (_x if i == 0 else 0), y - left.min(\"y\"), 0)\n",
    "        if i == 0:\n",
    "            _x = left.max(\"x\")\n",
    "        # add right card tilted in opposite direction\n",
    "        right = scene.add(\"card\").rotate(angle, \"z\")\n",
    "        shift = gap + left.max(\"x\") - right.min(\"x\")\n",
    "        right.at(shift, y - right.min(\"y\"), 0)\n",
    "        if i < n - 1:\n",
    "            ceil_x.append(right.max(\"x\"))\n",
    "        x = right.max(\"x\") + gap\n",
    "        max_y = right.max(\"y\") + gap\n",
    "        cards.extend([left, right])\n",
    "    # add horizontal ceiling cards\n",
    "    for i, x in enumerate(ceil_x):\n",
    "        z = max_y if i % 2 == 0 else max_y + gap\n",
    "        ceil = scene.add(\"card\").rotate(-90, \"z\").at(x, z, 0)\n",
    "        _y = max(_y, ceil.max(\"y\"))\n",
    "        cards.append(ceil)\n",
    "    return _x, _y + gap\n",
    "\n",
    "\n",
    "# build the house of cards pyramid\n",
    "_x, _y, _cards = -0.75, gap, []\n",
    "for i in reversed(range(n_stack)):\n",
    "    _x, _y = make_row(i + 1, _x, _y, _cards)\n",
    "\n",
    "# set material properties for all cards\n",
    "for card in _cards:\n",
    "    (\n",
    "        card.param.set(\"contact-gap\", gap)\n",
    "        .set(\"young-mod\", 30000)\n",
    "        .set(\"bend\", 1e6)\n",
    "        .set(\"friction\", 0.5)\n",
    "    )\n",
    "\n",
    "# add sphere projectile with initial velocity\n",
    "scene.add(\"sphere\").at(-2, 1, 0).jitter().velocity(2.3, 0, 0)\n",
    "\n",
    "# add invisible floor\n",
    "scene.add.invisible.wall([0, 0, 0], [0, 1, 0])\n",
    "\n",
    "# compile the scene and report stats\n",
    "scene = scene.build().report()\n",
    "\n",
    "# preview the initial scene\n",
    "scene.preview()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3f8610c-1392-4d22-9cd1-bf7c14d20ff5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# create a new session with the compiled scene\n",
    "session = app.session.create(scene)\n",
    "\n",
    "# set session parameters\n",
    "session.param.set(\"dt\", 0.01).set(\"min-newton-steps\", 32)\n",
    "\n",
    "# build this session\n",
    "session = session.build()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f9f0256-3de7-4a01-b05e-fb2b98794c1f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# start the simulation and live-preview the results\n",
    "session.start().preview()\n",
    "\n",
    "# also show simulation logs in realtime\n",
    "session.stream()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1fb38b89-3a6b-44fa-8629-6e6c21408fc8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# create an animation from the simulation results\n",
    "session.animate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "472bf8f9-9734-4af8-a3bc-0e1312aeab39",
   "metadata": {},
   "outputs": [],
   "source": [
    "# export the animation to file\n",
    "session.export.animation()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "726e97b1-1801-4354-bd37-04b5ad9119e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# this is for CI\n",
    "if app.ci:\n",
    "    assert session.finished()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
